package com.example.stock.service.impl;

import com.example.stock.entity.dao.AccountDao;
import com.example.stock.entity.eo.AccountEo;
import com.example.stock.vo.common.StockHoldVo;

import java.util.ArrayList;

import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson.JSON;
import com.example.stock.comm.AssetEnum;
import com.example.stock.comm.FreqEnum;
import com.example.stock.comm.StrategyEnum;
import com.example.stock.comm.TranEnum;
import com.example.stock.entity.eo.StStockTradeEo;
import com.example.stock.entity.eo.StockTradeEo;
import com.example.stock.entity.service.impl.StockTradeServiceImpl;
import com.example.stock.service.IServiceTrade;
import com.example.stock.service.IServiceRowData;
import com.example.stock.vo.DataVo;
import com.example.stock.vo.EmaTradeVo;
import com.example.stock.vo.ScoreVo;
import com.example.stock.vo.common.AccountVo;
import com.example.stock.vo.common.TradeDto;
import com.example.stock.vo.common.TradeVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class ServiceTradeImpl implements IServiceTrade {

    @Resource
    private StockTradeServiceImpl stockTradeServiceImpl;
    @Resource
    private IServiceRowData serviceRowData;
    @Resource
    private AccountDao accountDao;

    //买入佣金
    private static final Double CHARGE_RATE = 0.0001;
    //卖出佣金
    private static final Double SALE_CHARGE_RATE = 0.0001;

    public Map<String, List<StockTradeEo>> doMultiTrade(Map<String, List<TradeVo>> tradeVoMap, TranEnum tran, LocalDateTime startTime, int more) {
        Map<String, List<StockTradeEo>> ret = new HashMap<>();
        tradeVoMap.forEach((tsCode, tradeVos) -> {
            List<StockTradeEo> stockTradeEos = doTradeOne(tradeVos, tsCode, tran, startTime, more, 1000000.0);
            ret.put(tsCode, stockTradeEos);
        });
        return ret;
    }

    public List<StockTradeEo> doTradeOne(List<TradeVo> tradeVos, String tsCode, TranEnum tran, LocalDateTime startTime, int more, double initMoney) {

        //冗余数据处理
        tradeVos = tradeVos.stream()
                .skip(more)
                .filter(t -> t.getTradeTime().isAfter(startTime))
                .collect(Collectors.toList());

        List<StockTradeEo> stockTradeEos = new ArrayList<>();

        //初始化资金
        StStockTradeEo tradeEo = new StStockTradeEo()
                .setMoney(initMoney)
                .setHoldStatus((short) 0) //0空仓 1持仓
                .setPrice(tradeVos.get(0).getPrice())
                .setHoldTime(tradeVos.get(0).getTradeTime())
                .setProfit(0.0)
                .setTsCode(tsCode)
                .setStrategy(StrategyEnum.EMA_UP.name())
                .setCharge(0.0);
        stockTradeEos.add(BeanUtil.copyProperties(tradeEo, StockTradeEo.class));


        for (TradeVo tradeVo : tradeVos) {
            Double price = tradeVo.getPrice();
            if (tradeVo.isCanBuy()) {
                if (emptyHold(tradeEo)) {
                    //可用金额(扣除手续费)
                    Double totalM = tradeEo.getMoney() - (tradeEo.getMoney() * CHARGE_RATE);
                    tradeEo.setHoldStatus((short) 1);
                    tradeEo.setHoldTime(tradeVo.getTradeTime());
                    tradeEo.setCharge(tradeEo.getMoney() * CHARGE_RATE);
                    tradeEo.setHoldVol(totalM / price);
                    tradeEo.setMoney(totalM);
                    tradeEo.setPrice(price);
                    tradeEo.setProfit(0.0);

                    tradeEo.setTsCode(tsCode);
                    tradeEo.setStrategy(StrategyEnum.EMA_UP.name());

                    if (tradeVo instanceof EmaTradeVo) {
                        tradeEo.setCalcResult(((EmaTradeVo) tradeVo).getCalc1());
                    }
                    stockTradeEos.add(BeanUtil.copyProperties(tradeEo, StockTradeEo.class));
                }

            } else {
                if (!emptyHold(tradeEo)) {
                    if (TranEnum.T1.equals(tran)) {
                        //当天进行过交易
                        if (tradeVo.getTradeTime().getDayOfYear() == tradeEo.getHoldTime().getDayOfYear()) {
                            continue;
                        }
                    }
                    //金额 = 持仓*当前收盘价*(1-手续费)
                    Double money = tradeEo.getHoldVol() * price * (1.0 - SALE_CHARGE_RATE);
                    tradeEo.setProfit(money - tradeEo.getMoney());
                    tradeEo.setMoney(money);
                    tradeEo.setCharge(tradeEo.getHoldVol() * price * SALE_CHARGE_RATE);
                    tradeEo.setHoldVol(0.0);
                    tradeEo.setHoldStatus((short) 0);
                    tradeEo.setHoldTime(tradeVo.getTradeTime());
                    tradeEo.setPrice(tradeVo.getPrice());

                    tradeEo.setTsCode(tsCode);
                    tradeEo.setStrategy(StrategyEnum.EMA_UP.name());

                    if (tradeVo instanceof EmaTradeVo) {
                        tradeEo.setCalcResult(((EmaTradeVo) tradeVo).getCalc1());
                    }
                    stockTradeEos.add(BeanUtil.copyProperties(tradeEo, StockTradeEo.class));
                }
            }
        }

        TradeVo endTrade = tradeVos.get(tradeVos.size() - 1);

        //如果是空仓
        if (tradeEo.getHoldStatus() == (short) 0) {
            tradeEo.setPrice(endTrade.getPrice());
            tradeEo.setHoldTime(endTrade.getTradeTime());
            tradeEo.setProfit(0.0);
            tradeEo.setCharge(0.0);
            stockTradeEos.add(BeanUtil.copyProperties(tradeEo, StockTradeEo.class));
        } else {

            //金额 = 持仓*当前收盘价*(1-手续费)
            Double money = tradeEo.getHoldVol() * endTrade.getPrice() * (1.0 - SALE_CHARGE_RATE);
            tradeEo.setProfit(money - tradeEo.getMoney());
            tradeEo.setMoney(money);
            tradeEo.setCharge(tradeEo.getHoldVol() * endTrade.getPrice() * SALE_CHARGE_RATE);
            tradeEo.setHoldVol(0.0);
            tradeEo.setHoldStatus((short) 0);
            tradeEo.setHoldTime(endTrade.getTradeTime());
            tradeEo.setPrice(endTrade.getPrice());

            tradeEo.setTsCode(tsCode);
            tradeEo.setStrategy(StrategyEnum.EMA_UP.name());

            if (endTrade instanceof EmaTradeVo) {
                tradeEo.setCalcResult(((EmaTradeVo) endTrade).getCalc1());
            }
            stockTradeEos.add(BeanUtil.copyProperties(tradeEo, StockTradeEo.class));
        }
        return stockTradeEos;

    }

    private boolean emptyHold(StStockTradeEo hold) {
        return hold.getHoldStatus() == null || hold.getHoldStatus() == 0;
    }

    public List<StockTradeEo> combineTrade(Map<String, List<StockTradeEo>> doTradeMap) {
        List<StockTradeEo> stockTradeEos = new ArrayList<>();
        //获取交易记录 - 按时间升序
        final List<StockTradeEo> allStockTrade = new ArrayList<>();
        doTradeMap.forEach((k, v) -> allStockTrade.addAll(v));
        allStockTrade.sort(Comparator.comparing(StockTradeEo::getHoldTime));

        //tsCode -> 交易记录
        Map<String, List<StockTradeEo>> tsCode2Trades = allStockTrade.stream().collect(Collectors.groupingBy(StockTradeEo::getTsCode));
        //价格处理 统一初始价
        tsCode2Trades.forEach((k, v) -> handlerList(v));

        //第一天 时间
        LocalDateTime holdTime = allStockTrade.get(0).getHoldTime();

        //tsCode -> 初始化第一天
        Map<String, StockTradeEo> firstDay = new HashMap<>();
        tsCode2Trades.forEach((k, v) -> {
            StockTradeEo stockTradeEo = v.get(0);
            StockTradeEo first = BeanUtil.copyProperties(stockTradeEo, StockTradeEo.class);
            first.setHoldStatus((short) 0);
            first.setHoldTime(holdTime);
            firstDay.put(k, first);
        });


        //所有交易时间-已去重
        List<LocalDateTime> allTradeDay = allStockTrade.stream().map(StockTradeEo::getHoldTime).distinct().collect(Collectors.toList());
        validAllTradeDay(allTradeDay);
        for (LocalDateTime localDateTime : allTradeDay) {
            List<StockTradeEo> collect1 = allStockTrade.stream().filter(t -> t.getHoldTime().equals(localDateTime)).collect(Collectors.toList());
            for (StockTradeEo stockTradeEo : collect1) {
                firstDay.put(stockTradeEo.getTsCode(), stockTradeEo);
            }
            Collection<StockTradeEo> values = firstDay.values();
            stockTradeEos.add(new StockTradeEo()
                    .setHoldTime(localDateTime)
                    .setPrice(values.stream().mapToDouble(StockTradeEo::getPrice).sum() / (values.size()))
                    .setMoney(values.stream().mapToDouble(StockTradeEo::getMoney).sum() / (values.size()))
            );
        }
        return stockTradeEos;
    }

    //先排序
    private void handlerList(List<StockTradeEo> v) {
        double base = v.get(0).getPrice();
        v.forEach(eo -> eo.setPrice(eo.getPrice() / base * 1000000.0));
    }

    private void validAllTradeDay(List<LocalDateTime> allTradeDay) {
        LocalDateTime pre = LocalDateTime.MIN;
        for (LocalDateTime localDateTime : allTradeDay) {
            if (localDateTime.isBefore(pre)) {
                throw new RuntimeException("时间有误!");
            }
            pre = localDateTime;
        }
    }


    public ScoreVo compose(String compose, String freq, Double firstIndex, LocalDateTime startTime, LocalDateTime endTime) {
        List<StockTradeEo> tradeEos = new ArrayList<>();
        if (startTime.isAfter(LocalDateTime.now())) {
            throw new RuntimeException("时间有误");
        }
        if (null == endTime) {
            endTime = LocalDateTime.now();
        }
        do {
            //startTime 2001-01-01 00:00:00
            //endTime   2001-02-01 00:00:00
            LocalDateTime tmpEndTime = nextTime(startTime, freq);
            //todo
//            List<String> tsCodes = amountVolService.listTopTen(preTime(startTime,freq), startTime, compose);
            List<String> tsCodes = Arrays.asList("399300.SZ");
            System.out.println(tsCodes);
            Map<String, List<DataVo>> groupByCode = serviceRowData.getRowDataWithFreqBatchGroupByCode(tsCodes, startTime, tmpEndTime, FreqEnum.DAY, AssetEnum.INDEX, false, 0);
            List<DataVo> dataVos = new ArrayList<>();
            int size = tsCodes.size();
            double firstMoney = firstIndex;
            groupByCode.forEach((k, v) -> {
                adjust(v, firstMoney, size);
                dataVos.addAll(v);
            });

            Map<LocalDateTime, List<DataVo>> collect = dataVos.stream().collect(Collectors.groupingBy(DataVo::getTradeTime));
            List<StockTradeEo> collect1 = collect.values().stream()
                    .map(t -> {
                        if (t.size() != size) {
                            throw new RuntimeException("");
                        }
                        long count = t.stream().map(vo -> vo.getTradeTime()).distinct().count();
                        if (count != 1) {
                            throw new RuntimeException("时间有误!");
                        }
                        double sum = t.stream().mapToDouble(vo -> vo.getPrice()).sum();
                        return new StockTradeEo().setPrice(sum).setHoldTime(t.get(0).getTradeTime()).setTsCode("000000.00").setMoney(sum);
                    })
                    .sorted(Comparator.comparing(StockTradeEo::getHoldTime))
                    .collect(Collectors.toList());
            tradeEos.addAll(collect1);
            firstIndex = collect1.get(collect1.size() - 1).getPrice();
            startTime = tmpEndTime;
        } while (startTime.isBefore(endTime));
        System.out.println(JSON.toJSONString(tradeEos));
        return stockTradeServiceImpl.getScore(tradeEos);


    }

    @Override
    public Double doMultiTradeComp(Map<String, List<TradeVo>> calcTradeMap, TranEnum tran, LocalDateTime startTime, int more) {
        return doMultiTradeCompA(calcTradeMap, tran, startTime, more, 0);
    }

    @Override
    public Double doMultiTradeCompA(Map<String, List<TradeVo>> calcTradeMap, TranEnum tran, LocalDateTime startTime, int more, int i) {
        //当前价格 用来进行最后一次交易结算
        Map<String, Double> currentPrice = new HashMap<>();
        //init stock account
        AccountVo account = new AccountVo(calcTradeMap.size());
        //available money
        account.setMoney(1000000D);
        //hold stock detail
        HashMap<String, StockHoldVo> holds = new HashMap<>();
        account.setDetails(holds);

        List<TradeDto> tradeDtoList = new ArrayList<>();
        calcTradeMap.forEach((tsCode, tradeVos) -> {
            tradeVos.forEach(tradeVo -> {
                TradeDto dto = new TradeDto();
                dto.setTsCode(tsCode);
                dto.setTradeTime(tradeVo.getTradeTime());
                dto.setPrice(tradeVo.getPrice());
                dto.setCanBuy(tradeVo.isCanBuy());
                dto.setUpDown(((EmaTradeVo) tradeVo).getUpDown());
                dto.setResult(((EmaTradeVo) tradeVo).getCalc1());
                tradeDtoList.add(dto);
            });
        });
        Map<LocalDateTime, List<TradeDto>> collect = tradeDtoList.stream().collect(Collectors.groupingBy(TradeVo::getTradeTime));
        List<LocalDateTime> sortTime = collect.keySet().stream().sorted().collect(Collectors.toList());
        log.info("sortTime {}", JSON.toJSONString(sortTime));

        for (LocalDateTime localDateTime : sortTime) {
            List<TradeDto> currentTradeList = collect.get(localDateTime);
            for (TradeDto currentTrade : currentTradeList) {
                String tsCode = currentTrade.getTsCode();
                //current price
                currentPrice.put(tsCode, currentTrade.getPrice());
                if (currentTrade.isCanBuy()) {
                    if (isEmptyHold(tsCode, account)) {

                        if (i > 0 && currentTrade.getResult() > i) {
                            System.out.println("****************************" + JSON.toJSONString(currentTrade));
                            continue;
                        }

//                        if (tsCode.equals("399673.SZ") && currentTrade.getResult() > 40) { //创业板
////                            System.out.println("****************************" + JSON.toJSONString(currentTrade));
//                            continue;
//                        }
//
//                        if (tsCode.equals("399300.SZ") && currentTrade.getResult() > 40) { //沪深
////                            System.out.println("****************************" + JSON.toJSONString(currentTrade));
//                            continue;
//                        }
//
//                        if (tsCode.equals("000016.SH") && currentTrade.getResult() > 20) {  //上证
////                            System.out.println("****************************" + JSON.toJSONString(currentTrade));
//                            continue;
//                        }

                        //持仓明细
                        StockHoldVo hold_ = new StockHoldVo();
                        hold_.setTsCode(tsCode);

                        Double price = currentTrade.getPrice();
                        hold_.setPrice(price);

                        int num = getNum(currentTrade, account);
                        hold_.setNum(num);

                        hold_.setTime(currentTrade.getTradeTime());
                        holds.put(tsCode, hold_);

                        Double useMoney = price * num * (1 + CHARGE_RATE);
                        account.setMoney(account.getMoney() - useMoney);

                    }
                } else {
                    if (!isEmptyHold(tsCode, account)) {
                        if (TranEnum.T1.equals(tran)) {
                            //当天进行过交易
                            if (currentTrade.getTradeTime().getDayOfYear() ==
                                    holds.get(tsCode).getTime().getDayOfYear()) {
                                continue;
                            }
                        }
                        //卖出 股票明细
                        StockHoldVo sell = holds.remove(tsCode);
                        if (null == sell) {
                            throw new RuntimeException("程序bug1");
                        }

                        //资金入账
                        int num = sell.getNum();
                        Double price = currentTrade.getPrice();
                        Double money_ = num * price * (1 - SALE_CHARGE_RATE);
                        account.setMoney(account.getMoney() + money_);
                    }
                }

            }
        }


        Double money = account.getMoney();

        for (Map.Entry<String, StockHoldVo> en : holds.entrySet()) {
            String tsCode = en.getKey();
            StockHoldVo value = en.getValue();

            double saleMoney = value.getNum() * currentPrice.get(tsCode) * (1 - SALE_CHARGE_RATE);
            double buyMoney = value.getNum() * value.getPrice() * (1 - SALE_CHARGE_RATE);

//            log.info("==============最后一次交易 ==========盈亏 {} ",saleMoney - buyMoney);
            money = money + saleMoney;
        }
        return money;
    }

    private int getNum(TradeDto currentTrade, AccountVo account) {
        Double price = currentTrade.getPrice();
        Double emptyMoney = account.getMoney();
        int size = account.getDetails().size();
        double oneMoney = emptyMoney / (account.getPoolN() - size);
        double oneMoney_ = oneMoney * (1 - CHARGE_RATE);
        return (int) (oneMoney_ / price);

    }

    private boolean isEmptyHold(String tsCode, AccountVo accountVo) {
        boolean contains = accountVo.getDetails().keySet().contains(tsCode);
        return !contains;
    }

    private void adjust(List<DataVo> v, Double firstIndex, int size) {
        double eachFirst = firstIndex / size;
        double price = v.get(0).getPrice();
        v.forEach(t -> t.setPrice(t.getPrice() / price * eachFirst));
    }


    private LocalDateTime nextTime(LocalDateTime startTime, String freq) {
        FreqEnum byNameExt = FreqEnum.getByNameExt(freq);
        switch (byNameExt) {
            case DAY:
                return startTime.plusDays(1);
            case MON1:
                return startTime.plusMonths(1);
            case MON3:
                return startTime.plusMonths(3);
            case MON6:
                return startTime.plusMonths(6);
            case YEAR:
                return startTime.plusYears(1);
        }
        throw new RuntimeException("时间有误");
    }

    private LocalDateTime preTime(LocalDateTime startTime, String freq) {
        FreqEnum byNameExt = FreqEnum.getByNameExt(freq);
        switch (byNameExt) {
            case DAY:
                return startTime.plusDays(-1);
            case MON1:
                return startTime.plusMonths(-1);
            case MON3:
                return startTime.plusMonths(-3);
            case MON6:
                return startTime.plusMonths(-6);
            case YEAR:
                return startTime.plusYears(-1);
        }
        throw new RuntimeException("时间有误");
    }

}
